#include <benchmark/benchmark.h>
#include "pthread.h"
#include "threads.h"
#include "stdio.h"
#include "time.h"
#include "unistd.h"
#include "util.h"

void init()
{
    printf("Initializing...\n");
}

static void Bm_function_pthread_mutex_init(benchmark::State &state)
{
    pthread_mutex_t mutex;

    for (auto _ : state)
    {
        pthread_mutex_init(&mutex, nullptr);
    }
    state.SetBytesProcessed(state.iterations());
}

static void Bm_function_pthread_mutex_trylock(benchmark::State &state)
{
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, nullptr);

    while (state.KeepRunning()) {
        pthread_mutex_trylock(&mutex);
        pthread_mutex_unlock(&mutex);
    }
    pthread_mutex_destroy(&mutex);
    state.SetBytesProcessed(state.iterations());
}

static void BM_pthread_rwlock_tryread(benchmark::State& state) {
    pthread_rwlock_t lock;
    pthread_rwlock_init(&lock, nullptr);

    while (state.KeepRunning()) {
        pthread_rwlock_tryrdlock(&lock);
        pthread_rwlock_unlock(&lock);
    }

    pthread_rwlock_destroy(&lock);
    state.SetBytesProcessed(state.iterations());
}

static void BM_pthread_rwlock_trywrite(benchmark::State& state) {
    pthread_rwlock_t lock;
    pthread_rwlock_init(&lock, nullptr);

    while (state.KeepRunning()) {
        pthread_rwlock_trywrlock(&lock);
        pthread_rwlock_unlock(&lock);
    }

    pthread_rwlock_destroy(&lock);
    state.SetBytesProcessed(state.iterations());
}

static void Bm_function_tss_get(benchmark::State &state)
{
    tss_t key;
    tss_create(&key, nullptr);

    while (state.KeepRunning()) {
        tss_get(key);
    }

    tss_delete(key);
    state.SetBytesProcessed(state.iterations());
}

static void Bm_function_pthread_mutex_timedlock(benchmark::State &state)
{
    struct timespec tout;
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, nullptr);

    while (state.KeepRunning()) {
        pthread_mutex_lock(&mutex);
        tout.tv_sec += 1;
        pthread_mutex_timedlock(&mutex, &tout);
        pthread_mutex_unlock(&mutex);
    }

    pthread_mutex_destroy(&mutex);
    state.SetBytesProcessed(state.iterations());
}

static void Bm_function_pthread_rwlock_tryrdlock(benchmark::State &state)
{
    pthread_rwlock_t lock;
    pthread_rwlock_init(&lock, nullptr);

    while (state.KeepRunning()) {
        if (pthread_rwlock_tryrdlock(&lock) == 0)
        {
            pthread_rwlock_unlock(&lock);
        }
    }

    pthread_rwlock_destroy(&lock);
    state.SetBytesProcessed(state.iterations());
}

static void Bm_function_pthread_rwlock_timedrdlock(benchmark::State &state)
{
    struct timespec tout;
    time(&tout.tv_sec);
    tout.tv_nsec = 0;
    tout.tv_sec += 1;
    pthread_rwlock_t lock;
    pthread_rwlock_init(&lock, nullptr);

    while (state.KeepRunning()) {
        pthread_rwlock_timedrdlock(&lock, &tout);
        pthread_rwlock_unlock(&lock);
    }

    pthread_rwlock_destroy(&lock);
    state.SetBytesProcessed(state.iterations());
}

static void Bm_function_pthread_rwlock_timedwrlock(benchmark::State &state)
{
    struct timespec tout;
    time(&tout.tv_sec);
    tout.tv_nsec = 0;
    tout.tv_sec += 1;
    pthread_rwlock_t lock;
    pthread_rwlock_init(&lock, nullptr);

    while (state.KeepRunning()) {
        pthread_rwlock_timedwrlock(&lock, &tout);
        pthread_rwlock_unlock(&lock);
    }

    pthread_rwlock_destroy(&lock);
    state.SetBytesProcessed(state.iterations());
}

static void Bm_function_pthread_cond_timedwait(benchmark::State &state)
{
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec += 1;
    pthread_mutex_lock(&mutex);
    while (state.KeepRunning())
    {
        pthread_cond_timedwait(&cond, &mutex, &ts);
    }
    pthread_mutex_unlock(&mutex);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    state.SetBytesProcessed(state.iterations());
}

static void Bm_function_pthread_getspecific(benchmark::State &state)
{
    pthread_key_t key;
    pthread_key_create(&key, NULL);
    const int n = state.range(0);
    while (state.KeepRunning())
    {
        pthread_setspecific(key, (void*)&n);
        benchmark::DoNotOptimize((int *)pthread_getspecific(key));
    }
    pthread_key_delete(key);
    state.SetBytesProcessed(state.iterations());
}

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
void* thread_task1(void* arg)
{
    pthread_mutex_lock(&mutex1);
    pthread_cond_wait(&cond, &mutex1);
    sleep(1);
    pthread_mutex_unlock(&mutex1);
    return nullptr;
}

void* thread_task2(void* arg)
{
    pthread_mutex_lock(&mutex2);
    pthread_cond_wait(&cond, &mutex2);
    sleep(1);
    pthread_mutex_unlock(&mutex2);
    return nullptr;
}

void* broadcastNotifyMutex(void* arg)
{
    benchmark::State *statePtr = (benchmark::State *)arg;
    statePtr->ResumeTiming();
    benchmark::DoNotOptimize(pthread_cond_broadcast(&cond));
    statePtr->PauseTiming();
    return nullptr;
}

static void Bm_function_pthread_cond_broadcast(benchmark::State &state)
{
    while (state.KeepRunning())
    {
        state.PauseTiming();
        pthread_t thread_1, thread_2, thread_3;
        pthread_create(&thread_1, nullptr, thread_task1, nullptr);
        pthread_create(&thread_2, nullptr, thread_task2, nullptr);
        sleep(3);
        pthread_create(&thread_3, nullptr, broadcastNotifyMutex, &state);
        pthread_join(thread_1, nullptr);
        pthread_join(thread_2, nullptr);
        pthread_join(thread_3, nullptr);
    }
    state.SetBytesProcessed(state.iterations());
}

MUSL_BENCHMARK(Bm_function_pthread_mutex_init);
MUSL_BENCHMARK(Bm_function_pthread_mutex_trylock);
MUSL_BENCHMARK(BM_pthread_rwlock_tryread);
MUSL_BENCHMARK(BM_pthread_rwlock_trywrite);
MUSL_BENCHMARK(Bm_function_tss_get);
MUSL_BENCHMARK(Bm_function_pthread_mutex_timedlock);
MUSL_BENCHMARK(Bm_function_pthread_rwlock_tryrdlock);
MUSL_BENCHMARK(Bm_function_pthread_rwlock_timedrdlock);
MUSL_BENCHMARK(Bm_function_pthread_rwlock_timedwrlock);
MUSL_BENCHMARK(Bm_function_pthread_cond_timedwait);
MUSL_BENCHMARK_WITH_ARG(Bm_function_pthread_getspecific,"PTHREAD_KEY");
MUSL_BENCHMARK(Bm_function_pthread_cond_broadcast);